package pe.gob.foncodes.ssa.bs.common.controller;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import pe.gob.foncodes.ssa.bs.common.service.GenericService;
import pe.gob.foncodes.ssa.bs.common.util.Constante;
import pe.gob.foncodes.ssa.bs.common.util.DateHelper;
import pe.gob.foncodes.ssa.bs.common.util.ServiceException;

public class GenericController {

    protected static Logger log = Logger.getLogger(GenericController.class);
    /** Objeto para realizar el redireccionamiento hacia otras pantallas */
    private Map<String, Integer> accion;
    /**
     * Objeto para realizar busqueda (con la finalidad de no tocar el objeto de
     * la transaccin)
     */
    private Object beanBusqueda;
    /** Objeto que contiene los datos de la transaccin */
    private Object bean;
    /** Objecto que contiene los datos de la transacción para la auditoria - billy low */
    private Object beanAuditoria;
    private Object beanAuditoriaPK;
    /*Atributo principal del bean para guardar la PK*/
    private String mainCampoAuditoria;
    /**
     * Lista de objetos (con la finalidad de listar objetos en pantalla,
     * validar, etc.)
     */
    private List beanList;
    /**
     * El controlador se apoya en el service para realizar la persistencia de
     * los objetos
     */
    @Autowired
    private GenericService service;
    /**
     * Objeto de ayuda para realizar el mapping (repositorio y busqueda) de
     * cualquier tipo de objeto
     */
    protected Map<String, Object> map = new HashMap<String, Object>();
    /**
     * Ruta del archivo para descargar
     */
    private String pathToFile;
    /**
     * Nombre del archivo para descargar
     */
    private String fileName;
    /**
     * Lista de objetos del log (con la finalidad de listar objetos log en pantalla,
     * validar, etc.)
     */
    private List beanListLog;

    /**
     * Retorna el objeto <b>accion</b>. Lo instancia en caso que ste sea null
     * 
     * @return objeto <b>accion</b>
     */
    public Map<String, Integer> getAccion() {

        if (accion == null) {
            accion = new HashMap<String, Integer>();
        }

        return accion;
    }

    /**
     * Retorna el objeto <b>{@link #beanBusqueda}</b>
     * 
     * @return
     */
    public Object getBeanBusqueda() {
        return beanBusqueda;
    }

    /**
     * Setea el objeto parmetro al objeto <b>{@link #beanBusqueda}</b> del
     * controlador
     * 
     * @param beanBusqueda
     *            objeto para realizar la bsquedas
     */
    public void setBeanBusqueda(Object beanBusqueda) {
        this.beanBusqueda = beanBusqueda;
    }

    /**
     * Setea el objeto HashMap parmetro al objeto <b>{@link #accion}</b> del
     * controlador
     * 
     * @param accion
     *            hashMap con acciones
     */
    public void setAccion(Map<String, Integer> accion) {
        this.accion = accion;
    }

    public GenericService getService() {
        return service;
    }

    /**
     * Setea el objeto service parmetro al objeto <b>{@link #service}</b>
     * 
     * @param service
     */
    public void setService(GenericService service) {
        this.service = service;
    }

    /**
     * Realiza el cambio de tabs en un formulario JSF que contenga este objeto
     * 
     * @param event
     *            evento proveniente del actionListener
     */
    public void changeTabSet(ActionEvent event) {
        event.getComponent();
    }

    public void init() throws Exception {
    }

    /**
     * Setea un mensaje de error en pantalla.
     * 
     * @param e
     *            Exception con el mensaje de error que debe aparecer en
     *            pantalla.<br>
     *            <i>Por ejemplo: new
     *            Exception("El cdigo ingresado no es vlido")<i/>
     */
    protected void setMessageError(String clientId, String error) {
        FacesContext.getCurrentInstance().addMessage(clientId,
                new FacesMessage(FacesMessage.SEVERITY_FATAL, error, error));
    }

    /**
     * Setea un mensaje de error en pantalla.
     * 
     * @param e
     *            Exception con el mensaje de error que debe aparecer en
     *            pantalla.<br>
     *            <i>Por ejemplo: new
     *            Exception("El cdigo ingresado no es vlido")<i/>
     */
    protected void setMessageError(Exception e) {
        FacesContext.getCurrentInstance().addMessage(
                null,
                new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(), e.getLocalizedMessage()));
    }

    /**
     * Setea un mensaje de error en pantalla.
     * 
     * @param error
     *            cadena de texto que aparecer en pantalla.<br>
     *            <i>Por ejemplo: "El cdigo ingresado no es vlido"<i/>
     */
    protected void setMessageError(String error) {
        FacesContext.getCurrentInstance().addMessage(null,
                new FacesMessage(FacesMessage.SEVERITY_ERROR, error, error));
    }

    /**
     * Mtodo para validar los datos del objeto de transaccin ({@link #bean})
     * antes de realizar <b>{@link #save(ActionEvent)}</b>, <b>
     * {@link #update(ActionEvent)}</b> o <b>{@link #delete(ActionEvent)}</b>
     * 
     * @return true <b>(no errores)</b> o false <b>(hay errores)</b>
     * @throws Exception
     */
    public boolean validate() throws Exception {
        return true;
    }

    /**
     * Mtodo para realizar acciones antes de iniciar la validacin (
     * {@link #validate()}) de los datos del objeto de transaccin (
     * {@link #bean})
     * 
     * @param event
     *            evento proveniente del actionListener
     * @throws Exception
     */
    public void beforeUpdate(ActionEvent event) throws Exception {
        log.info("Start of Transaction: 'Update' at " + DateHelper.getFechaActual());
    }

    public void beforeUpdateSinID(ActionEvent event) throws Exception {
        log.info("Start of Transaction: 'Update' at " + DateHelper.getFechaActual());
    }
    
    /**
     * Mtodo para realizar acciones despus de haber llamado al mtodo
     * {@link #doUpdate(ActionEvent)}. <br/>
     * <i>Por ejemplo, se puede mostrar un mensaje confirmacin de transaccin
     * despus de haber actualizado.</i>
     * 
     * @param event
     *            evento proveniente del actionListener
     * @throws Exception
     */
    public void afterUpdate(ActionEvent event) throws Exception {
        log.info("End of Transaction: 'Update'");
    }

    /**
     * Mtodo que actualiza los datos del objeto de transaccin {@link #bean} en
     * base de datos
     * 
     * @param event
     *            evento proveniente del actionListener
     * @throws Exception
     */
    public void doUpdate(ActionEvent event) throws Exception {
        try {
            this.setBean(getService().save(getBean()));
        } catch (Exception e) {
            log.error(e);
        }
    }
    
    /**
     * Mtodo que realiza la secuencia completa para la actualizacin del
     * {@link #bean}: <br/>
     * 1. {@link #beforeUpdate(ActionEvent)} <br/>
     * 2. {@link #validate()} <br/>
     * 3. {@link #doUpdate(ActionEvent)} <br/>
     * 4. {@link #afterUpdate(ActionEvent)} <br/>
     * El update ser realizado siempre que el mtodo {@link #validate()}
     * devuelva <b>true</b>
     * 
     * @param event
     *            evento proveniente del actionListener
     * @throws Exception
     */
    public void update(ActionEvent event) throws Exception {
        try {
            beforeUpdate(event);
            if (validate()) {
                doUpdate(event);
                afterUpdate(event);
            }
        } catch (Exception ex) {
            log.error(ex.getStackTrace());
            setCatchError(ex);
        }
    }

    /**
     * Mtodo para realizar acciones antes de iniciar la eliminacion (
     * {@link #delete()}) de los datos del
     * 
     * @param event
     *            evento proveniente del actionListener
     * @throws Exception
     */
    public void beforeDelete(ActionEvent event) throws Exception {
        log.info("Start of Transaction: 'Delete' at " + DateHelper.getFechaActual());
    }

    public void beforeDeleteSinID(ActionEvent event) throws Exception {
        log.info("Start of Transaction: 'Delete' at " + DateHelper.getFechaActual());
    }
    /**
     * Mtodo que elimina los datos del objeto de transaccin {@link #bean} en
     * base de datos
     * 
     * @param event
     *            evento proveniente del actionListener
     * @throws Exception
     */
    public void doDelete(ActionEvent event) throws Exception {
        try {
            Object object = getBean();
            try {
                BeanUtils.setProperty(object, "flgActivo", Constante.ESTADO_INACTIVO);
            } catch (Exception e) {
                log.error(e);
            }
            service.save(object);
        } catch (Exception e) {
            log.error(e);
        }
    }

    public void doDeleteSinProp(ActionEvent event) throws Exception {
        try {
            getService().delete(getBean());
        } catch (Exception e) {
            log.error(e);
        }
    }
    
    /**
     * Mtodo para realizar acciones despus de haber llamado al mtodo
     * {@link #doDelete(ActionEvent)}. <br/>
     * <i>Por ejemplo, se puede mostrar un mensaje confirmacin de transaccin
     * despus de haber eliminado.</i>
     * 
     * @param event
     *            evento proveniente del actionListener
     * @throws Exception
     */
    public void afterDelete(ActionEvent event) throws Exception {
        log.info("End of Transaction: 'Delete'");
    }

    public void delete(ActionEvent event) throws Exception {
        try {
            beforeDelete(event);
            doDelete(event);
            afterDelete(event);
        } catch (Exception ex) {
            log.error(ex.getStackTrace(), ex);
            setCatchError(ex);
        }
    }

    /**
     * Mtodo para realizar acciones antes de iniciar la validacin (
     * {@link #validate()}) de los datos del objeto de transaccin (
     * {@link #bean})
     * 
     * @param event
     *            evento proveniente del actionListener
     * @throws Exception
     */
    public void beforeSave(ActionEvent event) throws Exception {
        log.info("Start of Transaction: 'Save' at " + DateHelper.getFechaActual());
    }

    /**
     * Mtodo que inserta los datos del objeto de transaccin {@link #bean} en
     * base de datos
     * 
     * @param event
     *            evento proveniente del actionListener
     * @throws Exception
     * 
     */
    public void doSave(ActionEvent event) throws Exception {
        try {
            this.setBean(getService().save(getBean()));
        } catch (Exception e) {
            log.error(e);
        }
    }

    
    /**
     * Mtodo que inserta los datos del objeto de transaccin {@link #bean} en
     * base de datos
     * 
     * @param event
     *            evento proveniente del actionListener
     * @throws Exception
     * 
     */
    public void doSaveSinProp(ActionEvent event) throws Exception {
        try {
            this.setBean(getService().save(getBean()));
        } catch (Exception e) {
            log.error(e);
        }
    }
    
    public void doSaveSinPropSinID(ActionEvent event) throws Exception {
        try {
            this.setBean(getService().save(getBean()));
        } catch (Exception e) {
            log.error(e);
        }
    }
    /**
     * Mtodo que realiza la secuencia completa para la insercion del
     * {@link #bean}: <br/>
     * 1. {@link #beforeSave(ActionEvent)} <br/>
     * 2. {@link #validate()} <br/>
     * 3. {@link #doSave(ActionEvent)} <br/>
     * 4. {@link #afterSave(ActionEvent)} <br/>
     * 
     * @param event
     *            evento proveniente del actionListener
     * @throws Exception
     */
    public void save(ActionEvent event) throws Exception {
        try {
            beforeSave(event);
            if (validate()) {
                doSave(event);
                afterSave(event);
            }
        } catch (Exception ex) {
            log.error(ex.getStackTrace());
            setCatchError(ex);
        }
    }

    /**
     * Mtodo para realizar acciones despus de haber llamado al mtodo
     * {@link #doSave(ActionEvent)}. <br/>
     * <i>Por ejemplo, se puede mostrar un mensaje confirmacin de transaccin
     * despus de haber insertado.</i>
     * 
     * @param event
     *            evento proveniente del actionListener
     * @throws Exception
     */
    public void afterSave(ActionEvent event) throws Exception {
        log.info("End of Transaction: 'Save'");
    }

    /**
     * Mtodo para limpiar o inicializar los objetos {@link #bean}. <br/>
     * 
     * @param event
     *            evento proveniente del actionListener
     * @throws Exception
     */
    public void clean(ActionEvent event) throws Exception {
        setBean(getBeanInstance());
    }

    /**
     * Metodo para buscar un objeto {@link #bean} segun el sid proveniente de un
     * formulario
     * 
     * @param event
     * @throws Exception
     */
    public void findById(ActionEvent event) throws Exception {
        Long id = Long.valueOf(getRequestParameter("sid"));
        this.bean = findById(id);
    }

    /**
     * Metodo para buscar un objeto {@link #bean} segun los atributos en
     * {@link #getBeanBusqueda}
     * 
     * @param event
     * @throws Exception
     */
    public void search(ActionEvent event) throws Exception {
        beanList = getService().findByObject(getBeanBusqueda());
        setBeanBusqueda(getBeanInstance());
    }

    /**
     * Metodo que se encarga de actualzar el listado de objetos {@link #bean}
     * 
     * @throws Exception
     */
    public void refreshBeanList() throws Exception {
        beanList = getService().findByObject(getBeanInstance());
    }

    /**
     * Metodo para buscar un {@link #bean} segun su id primario
     * 
     * @param id
     * @return
     * @throws ServiceException
     */
    protected Object findById(Long id) throws ServiceException {
        return doLoad(id);
    }

    /**
     * Metodo con el cual se obtiene una instancia nueva de {@link #bean}
     * 
     * @return un objeto segun el tipo que tenga {@link #bean}
     * @throws ServiceException
     */
    protected Object getBeanInstance() throws ServiceException {
        Class clazz;
        Object tmpObject;
        clazz = getBean().getClass();
        try {
            tmpObject = Class.forName(clazz.getName()).newInstance();
        } catch (InstantiationException e) {
            throw new ServiceException(e);
        } catch (IllegalAccessException e) {
            throw new ServiceException(e);
        } catch (ClassNotFoundException e) {
            throw new ServiceException(e);
        }
        return tmpObject;
    }

    /**
     * Metodo para cargar un objeto {@link #bean} segun el sid proveniente de un
     * formulario <br/>
     * 1. {@link #doLoad(ActionEvent)} <br/>
     * 
     * @param event
     * @throws Exception
     */
    public void load(ActionEvent event) throws Exception {
        Object bean = doLoad(getRequestParameter("sid"));
        setBean(bean);
    }

    public void loadLog(ActionEvent event) throws Exception {
        Object bean = doLoad(getRequestParameter("sid"));
        setBeanBusqueda(bean);
    }

    /**
     * Metodo para buscar un objeto {@link #bean} segun el id ingresado
     * 
     * @param event
     * @throws Exception
     */
    protected Object doLoad(Object propertyValue) throws ServiceException {
        try {
            return getService().findById(getBean().getClass(),
                    Long.parseLong((String) propertyValue));
        } catch (Exception e) {
            throw new ServiceException(e);
        }
    }

    protected Object doLoadLog(Object propertyValue) throws ServiceException {
        try {
            return getService().findById(getBeanBusqueda().getClass(),
                    Long.parseLong((String) propertyValue));
        } catch (Exception e) {
            throw new ServiceException(e);
        }
    }

    /**
     * Metodo para poder obtener o crear una session
     * 
     * @param create
     *            por ejemplo <br/>
     *            1. si es false se usa una session existente <br/>
     *            2. si es true se crea una session nueva <br/>
     *            - {@link #getRequest()} <br/>
     * @return un objeto session
     */
    protected HttpSession getSession(boolean create) {
        return getRequest().getSession(create);
    }

    /**
     * Metodo para obtener la session del Request
     * 
     * @return
     */
    protected HttpServletRequest getRequest() {
        return (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
    }

    /**
     * Metodo para obtener la Ip del cliente
     * @return
     */
    protected String getClientIp() {
        HttpServletRequest httpServletRequest = (HttpServletRequest) getRequest();
        return httpServletRequest.getRemoteAddr();
    }

    /**
     * Metodo para obtener la session del Response
     * 
     * @return
     */
    protected HttpServletResponse getResponse() {
        return (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
    }

    /**
     * Metodo para obtener un valor del request
     * 
     * @param name
     *            cadena donde se define el nombre del atributo del <br/>
     *            objeto en el Request
     * @return
     */
    protected String getRequestParameter(String name) {
        return (String) FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get(name);
    }

    /**
     * Metodo donde se ingresan los mensages de exito de los eventos <br/>
     * estos mensages se visualizaran en la pagina o formulario
     * 
     * @param msg
     *            mensaje de exito
     */
    protected void setMessageSuccess(String msg) {
        FacesContext.getCurrentInstance().addMessage(null,
                new FacesMessage(msg));
    }

    /**
     * Metodo donde se ingresan los mensages de exito de los eventos <br/>
     * estos mensages se visualizaran en la pagina o formulario
     * 
     * @param zone
     *            identificador del mensaje
     * @param msg
     *            mensaje de exito
     */
    protected void setMessageSuccess(String zone, String msg) {
        FacesContext.getCurrentInstance().addMessage(zone,
                new FacesMessage(msg));
    }

    /**
     * Devuelve el objeto {@link #bean} que es el objeto de transaccin
     * 
     * @return
     */
    public Object getBean() {
        return bean;
    }

    /**
     * Setea el objeto <b>bean</b> parmetro al objeto {@link #bean} del
     * controlador
     * 
     * @param bean
     */
    public void setBean(Object bean) {
        this.bean = bean;
    }

    /**
     * Retorna el objeto {@link #beanList}
     * 
     * @return
     * @throws Exception
     */
    public List getBeanList() throws Exception {
        return this.beanList;
    }

    /**
     * Setea el objeto <b>beanList</b> parmetro al objeto {@link #beanList} del
     * controlador
     * 
     * @param beanList
     */
    public void setBeanList(List beanList) {
        this.beanList = beanList;
    }

    /**
     * Retorna el objeto {@link #beanList}
     * 
     * @return
     * @throws Exception
     */
    public List getBeanListLog() throws Exception {
        return this.beanListLog;
    }

    /**
     * Setea el objeto <b>beanList</b> parmetro al objeto {@link #beanList} del
     * controlador
     * 
     * @param beanList
     */
    public void setBeanListLog(List beanListLog) {
        this.beanListLog = beanListLog;
    }

    /**
     * Metodo donde se obtiene un objeto de la session <br/>
     * - {@link #getWebContext()} <br/>
     * 
     * @param name
     *            nombre del objeto a buscar
     * @return
     */
    public Object getSpringBean(String name) {
        return getWebContext().getBean(name);
    }

    /**
     * Metodo donde se obtiene un objeto segun el nombre de la clase <br/>
     * - {@link #getWebContext()} <br/>
     * 
     * @param nameObj
     *            nombre de la clase a buscar
     * @return
     */
    public Object getSpringBean(Class nameObj) {
        return getSpringBean(ClassUtils.getShortName(nameObj));
    }

    /**
     * Metodo donde se obtiene un objeto de la session
     * 
     * @param nameObj
     *            nombre del objeto a buscar
     * @return
     */
    public Object getSpringBean(Object nameObj) {
        return getSpringBean(nameObj.getClass());
    }

    /**
     * Obtiene el Contenedor web de Spring
     * 
     * @return
     */
    public WebApplicationContext getWebContext() {
        return WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    }

    /**
     * Obtiene el contexto de ejecucin del Servlet
     * 
     * @return
     */
    public ServletContext getServletContext() {
        return (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
    }

    public void forward(String rule) {
        FacesContext fc = FacesContext.getCurrentInstance();
        fc.getApplication().getNavigationHandler().handleNavigation(fc, null,
                rule);
    }

    public Map getMap() {
        return map;
    }

    /**
     * Setea el objeto <b>map</b> parametro al objeto {@link #map} del
     * controlador
     * 
     * @param map
     */
    public void setMap(Map map) {
        this.map = map;
    }

    /**
     * Consulta el mtodo getSQLError de la clase BDHelper para obtener el tipo de error generado 
     * @param ex Exception
     */
    public void setCatchError(Exception ex) {
        setMessageError(pe.gob.foncodes.ssa.bs.common.util.BDHelper.getSQLError(ex));
    }

    public String getPathToFile() {
        return pathToFile;
    }

    public void setPathToFile(String pathToFile) {
        this.pathToFile = pathToFile;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    /**
     * @return the beanAuditoria
     */
    public Object getBeanAuditoria() {
        return beanAuditoria;
    }

    /**
     * @param beanAuditoria the beanAuditoria to set
     */
    public void setBeanAuditoria(Object beanAuditoria) {
        this.beanAuditoria = beanAuditoria;
    }

    public Object getBeanAuditoriaPK() {
        return beanAuditoriaPK;
    }

    public void setBeanAuditoriaPK(Object beanAuditoriaPK) {
        this.beanAuditoriaPK = beanAuditoriaPK;
    }

    public String getMainCampoAuditoria() {
        return mainCampoAuditoria;
    }

    public void setMainCampoAuditoria(String mainCampoAuditoria) {
        this.mainCampoAuditoria = mainCampoAuditoria;
    }
    
}
